Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Precompiled deps #107

Merged
merged 41 commits into from
Oct 5, 2023
Merged

Precompiled deps #107

merged 41 commits into from
Oct 5, 2023

Conversation

varsill
Copy link
Contributor

@varsill varsill commented Aug 7, 2023

closes membraneframework/membrane_core#603
This PR adds an ability to specify precompiled dependencies.
Its usage can be seen here: membraneframework/membrane_h264_ffmpeg_plugin#92

@varsill varsill marked this pull request as ready for review August 30, 2023 12:17
@varsill varsill requested a review from mat-hek August 30, 2023 12:17
Comment on lines 193 to 195
:curl -> "curl --fail -L -o #{dest} #{url}"
:wget -> "wget -O #{dest} #{url}"
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use an elixir/erlang library for that, for example https://hexdocs.pm/req/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -0,0 +1,69 @@
defmodule Bundlex.PrecompiledDependency do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we spoke, let's remove the behaviour in favour of accepting config

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed, I will do it later if we assume that this approach is still valid

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behaviour has been removed ;)

assert {_output, 0} = System.cmd("sh", ["-c", "mix test 1>&2"], cd: "test_projects/example")
System.cmd("sh", ["-c", "mix test 1>&2"], cd: "test_projects/example")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove the assertion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have revoked it ;)

require Logger
alias Bundlex.{Output, PrecompiledDependency}

@precompiled_path "_build/#{Mix.env()}/precompiled/"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Mix.Project.build_path() and name the dir with prefix, e.g. 'bundlex_precompiled'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 200 to 215
Map.take(dependency, [:includes, :libs, :lib_dirs, :pkg_configs, :linker_flags, :deps]),
Map.take(dependency, [:includes, :libs, :lib_dirs, :os_deps, :linker_flags, :deps]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless we don't want to release a new version, we should still support pkg_configs, just deprecate them

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 109 to 115
defp remove_lib_prefix(libname) do
if String.starts_with?(libname, "lib") do
String.slice(libname, 3..-1)
else
libname
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defp remove_lib_prefix(libname) do
if String.starts_with?(libname, "lib") do
String.slice(libname, 3..-1)
else
libname
end
end
defp remove_lib_prefix("lib" <> libname), do: libname
defp remove_lib_prefix(libname), do: libname

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, thanks :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -0,0 +1,204 @@
defmodule Bundlex.OSDeps do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about moving this to Budlex.Toolchain.Common.Unix.OsDeps, as it's quite unix-specific, though the name would get scaringly java-style :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will move it ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@varsill varsill requested a review from mat-hek September 4, 2023 11:45
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
lib/bundlex.ex Outdated
Comment on lines 14 to 20
It consists of:
* architecture - e.g. `x86_64` or `arm64`
* vendor - e.g. `pc`
* operating system - e.g. `linux` or `freebsd`
"""
@type target_triplet ::
{architecture :: String.t(), vendor :: String.t(), operating_system :: String.t()}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make it a struct for more flexibility and easier access to fields. Also, maybe we could put the version of the operating system in a separate field, so we didn't have to pattern match it. I'd consider converting strings to atoms as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about putting the operating system's version as a separate field - we would need to be aware of all possible values of the operating system returned by the :erlang.system_info, since some of them might be just a system name (e.g. linux), and some of them contain the version (e.g. darwin20.6.0).
For a person who needs to use the os value (that is a person who prepares the precompiled dependency) it's not that much effort to pattern match on something like "darwin"<>_version since it's already aware what the os string look like on a given system.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also why I wouldn't convert the names into atoms, as it will make such a pattern matching more difficult (or impossible)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so maybe we could have a function like Bundlex.family() that would convert the OS to one of the well-known values? Then we could gradually improve it to support more systems if needed

{PrecompiledFFmpeg, [:libswscale, :libavcodec]},
:libpng
]
{get_ffmpeg_path(), ["libswscale", "libavcodec"]},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this implicit pkg-config as a default, and maybe we could accept a list of 'providers', which could be either {:precompiled, url} or :pkg_config or nil. So in this case we'd have

Suggested change
{get_ffmpeg_path(), ["libswscale", "libavcodec"]},
{[get_ffmpeg_path(), :pkg_config], ["libswscale", "libavcodec"]},

And get_ffmpeg_path() would return {:precompiled, url} or nil. This way is more verbose but less implicit and the user can decide whether to take the precompiled or OS package first ;) WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done ;)

lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
@@ -152,7 +136,7 @@ defmodule Bundlex.OSDeps do
try do
File.mkdir(package_path)
temporary_destination = "#{@precompiled_path}/temporary"
download(url, temporary_destination)
download(precompiled_dependency_url, temporary_destination)
System.shell("tar -xf #{temporary_destination} -C #{package_path} --strip-components 1")
System.shell("rm #{temporary_destination}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
System.shell("rm #{temporary_destination}")
File.rm!(temporary_destination)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -173,32 +157,19 @@ defmodule Bundlex.OSDeps do
String.split(url, "/")
|> Enum.at(-1)
|> String.split(".")
|> Enum.reject(&(&1 in ["tar", "xz"]))
|> Enum.reject(&(&1 in ["tar", "xz", "gz"]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've bumped into https://github.com/ricn/zarex, maybe it's worth considering?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, done

case System.shell(command) do
{_, 0} -> :ok
_other -> :error
if response.status == 200 do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: case & pattern match would look better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

require Logger
alias Bundlex.Output

@precompiled_path "#{Mix.Project.build_path()}/bundlex_precompiled/"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how this path can change, so I'd make it defp just in case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

README.md Show resolved Hide resolved
[]
end

defp resolve_single_os_dep(providers, lib_names) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, you can resolve all the flags and paths here. If you add them to existing fields in Native, there'll be no need for the resolved_os_deps field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

{{precompiled_dependency, package_path}, lib_names}
end
end)
# defp parse_os_deps(os_deps) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed ;)

lib/bundlex.ex Outdated Show resolved Hide resolved
lib/bundlex/toolchain/common/unix/os_deps.ex Outdated Show resolved Hide resolved
Comment on lines 154 to 170
try do
File.mkdir(package_path)
temporary_destination = "#{@precompiled_path}/temporary"
File.mkdir!(package_path)
temporary_destination = "#{get_precompiled_path()}/temporary"
download(precompiled_dependency_url, temporary_destination)
System.shell("tar -xf #{temporary_destination} -C #{package_path} --strip-components 1")
System.shell("rm #{temporary_destination}")

{_output, 0} =
System.shell(
"tar -xf #{temporary_destination} -C #{package_path} --strip-components 1"
)

File.rm!(temporary_destination)
package_path
rescue
e ->
Logger.warning("Couldn't download the dependency due to: #{inspect(e)}.")
IO.warn("Couldn't download the dependency due to: #{inspect(e)}.")
:unavailable
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This try shouldn't rescue exception from File.mkdir!/1 function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, why do we need try here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there is a possibility that in example, due to network conditions, the dependency won't be downloaded

README.md Outdated
@@ -128,6 +128,17 @@ Configuration of each native may contain following options:
* `libs` - Names of libraries to link (empty list by default).
* `pkg_configs` - (deprecated) Names of libraries for which the appropriate flags will be
obtained using pkg-config (empty list by default).
* `os_deps` - List of external OS dependencies. It's a list of elements in form of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `os_deps` - List of external OS dependencies. It's a list of elements in form of
* `os_deps` - List of external OS dependencies. It's a list of tuples in the form of

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 19 to 25
compiler_flags =
Enum.uniq(cflags_list)
|> Enum.join(" ")

libs_flags =
Enum.uniq(libs_list)
|> Enum.join(" ")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't these be just concatenated with respective native.*_flags?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have slightly modified it

@@ -40,6 +40,7 @@ defmodule Bundlex.Project do
includes: [String.t()],
lib_dirs: [String.t()],
libs: [String.t()],
os_deps: [Bundlex.Native.os_dep()],
Copy link
Member

@mat-hek mat-hek Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not documented in the typedoc. Right now, this typedoc is copied to readme so we have to update both places. Maybe we can replace it in the readme with an example and a link to the typedoc?

get_flags_for_pkg_config(lib_names, :libs, app)}
]
rescue
_e ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we silence any error that may have happened. I'd print it, otherwise, there's no way to see what went wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we can print something like 'Failed to fetch the OS libs X, Y, Z via pkg-config, trying other ways' and print the original error(s) only if no provider succeeds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

end

defp resolve_single_os_dep([], lib_names, _app) do
IO.warn("Couldn't load OS dependencies for libraries: #{inspect(lib_names)}.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should raise here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 131 to 140
# defp parse_os_deps(os_deps) do
# Enum.map(os_deps, fn
# {:precompiled, precompiled_dependency_url, lib_name_or_names} ->
# lib_names = Bunch.listify(lib_name_or_names)
# {precompiled_dependency_url, lib_names}

# lib_name ->
# {:pkg_config, lib_name}
# end)
# end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover

try do
File.mkdir!(package_path)
temporary_destination = "#{get_precompiled_path()}/temporary"
download(precompiled_dependency_url, temporary_destination)

Copy link
Member

@FelonEkonom FelonEkonom Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IO.warn in rescuebelow will print a misleading warning in case of error thrown from System.shell or File.rm!


true ->
raise "Unknown flag type: #{inspect(flags_type)}"
"-I#{full_include_path}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"-I#{full_include_path}"
[ "-I#{full_include_path}"]

Not having a list here might cause an error thrown from Enum.zip at line 17

providers = Bunch.listify(provider_or_providers)
lib_names = Bunch.listify(lib_name_or_names)

resolve_single_os_dep(providers, lib_names, native.app)
end)
|> Enum.unzip()
|> then(&{List.flatten(elem(&1, 0)), List.flatten(elem(&1, 1))})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hard to read, let's do that below, alongside Enum.uniq

@FelonEkonom FelonEkonom self-requested a review October 2, 2023 10:25
Comment on lines 19 to 21
compiler_flags =
Enum.uniq(cflags_list)
List.flatten(cflags_list)
|> Enum.uniq()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
compiler_flags =
Enum.uniq(cflags_list)
List.flatten(cflags_list)
|> Enum.uniq()
compiler_flags = cflags_list |> List.flatten() |> Enum.uniq()

Comment on lines 23 to 25
libs_flags =
Enum.uniq(libs_list)
List.flatten(libs_list)
|> Enum.uniq()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
libs_flags =
Enum.uniq(libs_list)
List.flatten(libs_list)
|> Enum.uniq()
libs_flags = libs_list |> List.flatten() |> Enum.uniq()

@Noarkhh Noarkhh merged commit 27a8513 into master Oct 5, 2023
3 checks passed
@Noarkhh Noarkhh deleted the precompiled_deps branch October 5, 2023 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add API to handle precompiled dependencies in Bundlex
4 participants